﻿using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Red.Controls.Behaviors;
using Red.Controls.Converters;
using Red.Controls.ViewModels;

namespace Red.Controls.DataDriven
{
    [InteropControl( "Float:Ranged", "Single:Ranged", "Double:Ranged", "float:Ranged" )]
    public class RangedNumberBox : DockPanel
    {
        #region Internal converters 

        /// <summary>
        /// Converter used to cast boolean 'useSlider' value to slider's visibility
        /// </summary>
        private class BooleanStringToVisibilityConverter : IValueConverter
        {
            public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
            {
                bool val = false;

                if ( value is string )
                {
                    Boolean.TryParse( (string)value, out val );
                }

                return val ? Visibility.Visible : Visibility.Collapsed;
            }

            public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
            {
                throw new NotImplementedException();
            }
        }

		/// <summary>
        /// Converter used to change slider's tick frequency based on range of possible values
        /// </summary>
        private class ValueDividingConverter : IMultiValueConverter
        {
            public object Convert( object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture )
            {
                double result = 0.1;

                string maxString = values[0] as string;
                string minString = values[1] as string;
                double max = 0, min = 0;

                if ( !string.IsNullOrWhiteSpace( maxString ) && !string.IsNullOrWhiteSpace( minString ) 
                    && double.TryParse( maxString, out max ) && double.TryParse( minString, out min ) )
                {
                    double range = max - min;

                    // In the event that we have a tiny range ( < 1 ), adjust the step until we have a minimum of 10 increments
                    while ( range < 1 )
                    {
                        result /= 10;
                        range *= 10;
                    }
                }

                return result;
            }

            public object[] ConvertBack( object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture )
            {
                throw new NotImplementedException();
            }
        }

        #endregion

        #region Data

        protected NumberBox _numBox;
        protected Slider _slider;
        protected Grid _internalGrid;

        #endregion

        #region Properties

        public static readonly DependencyProperty NarrowModeProperty =
            DependencyProperty.Register( "NarrowMode", typeof( bool ), typeof( RangedNumberBox ) );

        public bool NarrowMode
        {
            get { return (bool)GetValue( NarrowModeProperty ); }
            set { SetValue( NarrowModeProperty, value ); }
        }

        #endregion

        #region C-tor

        public RangedNumberBox()
        {
            Initialized += RangedValueControl_Initialized;
            SizeChanged += RangedValueControl_SizeChanged;
        }

        #endregion

        #region Event handlers

        private void RangedValueControl_SizeChanged( object sender, SizeChangedEventArgs e )
        {
            UpdateNarrowMode();
        }
        
        private void Slider_IsVisibleChanged( object sender, DependencyPropertyChangedEventArgs e )
        {
            UpdateNarrowMode();
        }

        private async void RangedValueControl_Initialized( object sender, EventArgs e )
        {
            UpdateNarrowMode();
            CreateInternalGrid();
            CreateNumBox();
            CreateSlider();
            
            await BindData();
        }

        #endregion Event handlers

        #region Private methods

        private void UpdateNarrowMode()
        {
            NarrowMode = ActualWidth < NarrowModeSettings.NarrowModeSize || _slider.Visibility == Visibility.Collapsed;
        }

        private async Task BindData()
        {
            var parentItem = (DataItem)DataContext;
            var minItem = await parentItem.DigUpSubItemAsync( "_min" );
            var maxItem = await parentItem.DigUpSubItemAsync( "_max" );
            var useSliderItem = await parentItem.DigUpSubItemAsync( "_useSlider" );

            SetupNumBox( minItem, maxItem );
            SetupSlider( minItem, maxItem, useSliderItem );
        }

        private void SetupNumBox( DataItem minItem, DataItem maxItem )
        {
            _numBox.SetBinding( NumberBox.MinProperty, new Binding( "Value" )
            {
                Source = minItem,
                Converter = new StringToNullableDoubleConverter()
            } );

            _numBox.SetBinding( NumberBox.MaxProperty, new Binding( "Value" )
            {
                Source = maxItem,
                Converter = new StringToNullableDoubleConverter()
            } );


            _numBox.SetBinding( NumberBox.IsReadOnlyProperty, new Binding( "IsReadOnly" )
            {
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            } );
        }

        private void SetupSlider( DataItem minItem, DataItem maxItem, DataItem useSliderItem )
        {
            _slider.SetBinding( Slider.ValueProperty, new Binding( "Value" )
            {
                Mode = BindingMode.TwoWay,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                Converter = new StringToNullableDoubleConverter()
            } );

            _slider.SetBinding( Slider.VisibilityProperty, new Binding( "Value" )
            {
                Source = useSliderItem,
                Converter = new BooleanStringToVisibilityConverter()
            } );

            _slider.SetBinding( Slider.MinimumProperty, new Binding( "Value" )
            {
                Source = minItem,
                Converter = new StringToNullableDoubleConverter()
            } );

            _slider.SetBinding( Slider.MaximumProperty, new Binding( "Value" )
            {
                Source = maxItem,
                Converter = new StringToNullableDoubleConverter()
            } );

            _slider.SetBinding( Slider.IsEnabledProperty, new Binding( "IsReadOnly" )
            {
                Converter = new BoolInverter()
            } );

            if ( !maxItem.Type.ToLower().Contains( "int" ) )
            {
                var binding = new MultiBinding()
                {
                    Converter = new ValueDividingConverter()
                };

                binding.Bindings.Add( new Binding( "Value" )
                {
                    Source = maxItem
                } );

                binding.Bindings.Add( new Binding( "Value" )
                {
                    Source = minItem
                } );
                _slider.SetBinding( Slider.TickFrequencyProperty, binding );
            }
        }

        private void CreateSlider()
        {
            _slider = new Slider();
            _slider.IsVisibleChanged += Slider_IsVisibleChanged;
            _slider.TickFrequency = 0.1;
            _slider.IsSnapToTickEnabled = true;
            _slider.HorizontalAlignment = HorizontalAlignment.Stretch;
            _slider.Margin = new Thickness( 0, 0, 4, 0 );

            _internalGrid.Children.Add( _slider );
        }

        private void CreateNumBox()
        {
            _numBox = new NumberBox();
            _numBox.MinWidth = 30;
            _numBox.XUpdateModifier = Behaviors.ValueUpdateModifier.Hundredths;
            _numBox.YUpdateModifier = Behaviors.ValueUpdateModifier.Decimal;
            
            _internalGrid.Children.Add( _numBox );
            Grid.SetColumn( _numBox, 1 );
        }

        private void CreateInternalGrid()
        {
            _internalGrid = new Grid();
            _internalGrid.ColumnDefinitions.Add( new ColumnDefinition() );
            _internalGrid.ColumnDefinitions.Add( new ColumnDefinition() );
            Children.Add( _internalGrid );

            ApplyColumnStyle( _internalGrid.ColumnDefinitions[0], new GridLength( 2, GridUnitType.Star ), new GridLength( 0 ) );
            ApplyColumnStyle( _internalGrid.ColumnDefinitions[1], new GridLength( 1, GridUnitType.Star ), new GridLength( 1, GridUnitType.Star ) );
        }

        private void ApplyColumnStyle( ColumnDefinition destCol, GridLength regularWidth, GridLength narrowModeWidth )
        {
            DataTrigger trigger = new DataTrigger()
            {
                Binding = new Binding( "NarrowMode" ) { Source = this },
                Value = true
            };
            trigger.Setters.Add( new Setter( ColumnDefinition.WidthProperty, narrowModeWidth ) );

            Style colStyle = new Style( typeof( ColumnDefinition ) );
            colStyle.Setters.Add( new Setter( ColumnDefinition.WidthProperty, regularWidth ) );
            colStyle.Triggers.Add( trigger );

            destCol.SetValue( ColumnDefinition.StyleProperty, colStyle );
        }

        #endregion Private methods
    }

    [InteropControl( "Int:Ranged", "int:Ranged", "Int8:Ranged", "Int16:Ranged", "Int32:Ranged", "Uint8:Ranged", "Uint16:Ranged", "Uint32:Ranged" )]
    public class RangedIntNumberBox : RangedNumberBox
    {
        public RangedIntNumberBox()
        {
            Initialized += IntRangedValueControl_Initialized;
        }

        void IntRangedValueControl_Initialized( object sender, EventArgs e )
        {
            _numBox.XUpdateModifier = ValueUpdateModifier.Total;
            _numBox.YUpdateModifier = ValueUpdateModifier.Total;
            _slider.TickFrequency = 1;
        }
    }
}
